﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Game500.ClientServerCommon.Src.Networking
{
  public delegate void DataReceivedDelegate(NetworkPacket receivedPacket);

  public delegate void ErrorDelegate(Exception e);

  public class DataReceivedEventArgs : EventArgs
  {
    private readonly byte[] myData;
    private readonly int myDataLength;

    public DataReceivedEventArgs(byte[] data, int dataLength)
    {
      myData = data;
      myDataLength = dataLength;
    }

    public byte[] Data
    {
      get { return myData; }
    }

    public int DataLength
    {
      get { return myDataLength; }
    }

    public int BytesProcessed { get; set; }
  }

  public class NetworkPacketStream
  {
    private const int ReceiveBufferSize = 1024 * 1024;

    private readonly byte[] myBuffer = new byte[ReceiveBufferSize];
    private readonly Queue<NetworkPacket> myImportantPackets = new Queue<NetworkPacket>();
    private readonly Stream myStream;
    private bool myIsSending;
    private ReceiveState myReceiveState;

    public NetworkPacketStream(Stream stream)
    {
      myStream = stream;
    }

    public event DataReceivedDelegate DataReceivedEvent;
    public event ErrorDelegate ErrorEvent;

    public void StartListen()
    {
      try
      {
        myReceiveState = ReceiveState.SizeReceiving;
        myStream.BeginRead(myBuffer, 0, sizeof(int), ReceiveCallback, this);
      }
      catch (IOException e)
      {
        ErrorEvent(e);
      }
    }

    public bool IsSending()
    {
      return myIsSending;
    }

    private void ReceiveCallback(IAsyncResult ar)
    {
      int bytesRead;
      try
      {
        bytesRead = myStream.EndRead(ar);
      }
      catch (IOException e)
      {
        ErrorEvent(e);
        return;
      }
      if (myReceiveState == ReceiveState.SizeReceiving)
      {
        int packetSize = BitConverter.ToInt32(myBuffer, 0);
        myReceiveState = ReceiveState.DataReceiving;
        try
        {
          myStream.BeginRead(myBuffer, 0, packetSize, ReceiveCallback, this);
        }
        catch (Exception e)
        {
          ErrorEvent(e);
        }
      }
      else if (myReceiveState == ReceiveState.DataReceiving)
      {
        try
        {
          NetworkPacket packet;
          if (NetworkPacket.TryParsePacket(myBuffer, bytesRead, out packet))
          {
            DataReceivedEvent(packet);
          }
          myReceiveState = ReceiveState.SizeReceiving;
          myStream.BeginRead(myBuffer, 0, sizeof(int), ReceiveCallback, this);
        }
        catch (Exception e)
        {
          ErrorEvent(e);
        }
      }
    }

    private void DoSendPacket(NetworkPacket packet)
    {
      byte[] data = packet.GetBytes();
      var rawData = new byte[data.Length + sizeof(int)];
      byte[] sizeArray = BitConverter.GetBytes(data.Length);
      Array.Copy(sizeArray, 0, rawData, 0, sizeArray.Length);
      Array.Copy(data, 0, rawData, sizeArray.Length, data.Length);
      SendRawData(rawData, rawData.Length);
    }

    private void DoSendImportantPackets()
    {
      lock (myImportantPackets)
      {
        if (myImportantPackets.Count > 0)
        {
          NetworkPacket packet = myImportantPackets.Dequeue();
          DoSendPacket(packet);
        }
        else
        {
          myIsSending = false;
        }
      }
    }

    private void SendRawData(byte[] data, int length)
    {
      myIsSending = true;
      try
      {
        myStream.BeginWrite(data, 0, length, SendCallback, this);
      }
      catch (IOException e)
      {
        ErrorEvent(e);
        myIsSending = false;
      }
    }

    public bool TrySendNetworkPacket(NetworkPacket packet)
    {
      byte[] data = packet.GetBytes();
      var rawData = new byte[data.Length + sizeof(int)];
      byte[] sizeArray = BitConverter.GetBytes(data.Length);
      Array.Copy(sizeArray, 0, rawData, 0, sizeArray.Length);
      Array.Copy(data, 0, rawData, sizeArray.Length, data.Length);
      return TrySendRawData(rawData, rawData.Length);
    }

    public bool TrySendMultipleNetworkPackets(IEnumerable<NetworkPacket> packetList)
    {
      using (var resultStream = new MemoryStream())
      {
        foreach (NetworkPacket networkPacket in packetList)
        {
          byte[] data = networkPacket.GetBytes();
          byte[] sizeArrray = BitConverter.GetBytes(data.Length);
          resultStream.Write(sizeArrray, 0, sizeArrray.Length);
          resultStream.Write(data, 0, data.Length);
        }
        return TrySendRawData(resultStream.GetBuffer(), (int)resultStream.Length);
      }
    }

    public bool TrySendRawData(byte[] data, int length)
    {
      if (myIsSending)
      {
        return false;
      }
      SendRawData(data, length);
      return myIsSending;
    }

    private void SendCallback(IAsyncResult ar)
    {
      try
      {
        myStream.EndWrite(ar);
      }
      catch (IOException e)
      {
        ErrorEvent(e);
      }
      finally
      {
        DoSendImportantPackets();
      }
    }

    public void SendPacket(NetworkPacket packet)
    {
      lock (myImportantPackets)
      {
        myImportantPackets.Enqueue(packet);
      }
      DoSendImportantPackets();
    }

    #region Nested type: ReceiveState

    private enum ReceiveState
    {
      SizeReceiving,
      DataReceiving
    }

    #endregion
  }
}